package com.fasterxml.jackson.databind.util;

import com.fasterxml.jackson.core.SerializableString;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.cfg.EnumFeature;
import com.fasterxml.jackson.databind.introspect.AnnotatedClass;

import java.util.Arrays;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

public class EnumUtils {
    private static final ConcurrentMap<CacheKey, SerializableString[]> cache = new ConcurrentHashMap<>();

    static class CacheKey {
        private final Object[] params;
        private final int hashCode;

        public CacheKey(Object... params) {
            this.params = params;
            this.hashCode = Arrays.deepHashCode(params);
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            CacheKey cacheKey = (CacheKey) o;
            return Arrays.deepEquals(params, cacheKey.params);
        }

        @Override
        public int hashCode() {
            return hashCode;
        }
    }

    public static <T extends Enum<T>> SerializableString serialize(Enum<T> value, SerializationConfig config) {
        Class<?> enumClass = value.getClass();
        boolean enabledLowerCase = config.isEnabled(EnumFeature.WRITE_ENUMS_TO_LOWERCASE);
        CacheKey cacheKey = new CacheKey(enumClass, enabledLowerCase);
        SerializableString[] serializableStrings = cache.get(cacheKey);
        if (serializableStrings != null) return serializableStrings[value.ordinal()];
        Class<? extends Enum<?>> enumCls = ClassUtil.findEnumType(enumClass);
        Enum<?>[] enumValues = enumCls.getEnumConstants();
        if (enumValues == null) {
            throw new IllegalArgumentException("Cannot determine enum constants for Class " + enumClass.getName());
        }
        AnnotatedClass annotatedClass = config.introspectClassAnnotations(enumCls).getClassInfo();
        String[] names = config.getAnnotationIntrospector().findEnumValues(config, annotatedClass, enumValues, new String[enumValues.length]);
        SerializableString[] textual = new SerializableString[enumValues.length];
        for (int i = 0, len = enumValues.length; i < len; ++i) {
            Enum<?> en = enumValues[i];
            String name = names[i];
            if (name == null) name = en.name();
            if (enabledLowerCase) name = name.toLowerCase();
            textual[en.ordinal()] = config.compileString(name);
        }
        cache.put(cacheKey, textual);
        return textual[value.ordinal()];
    }
}
